/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*
 *	Memory management: malloc(), realloc(), free().
 *
 *	The following #-parameters may be redefined:
 *	SEGMENTED: if defined, memory requests are assumed to be
 *		non-contiguous across calls of GETCORE's.
 *	GETCORE: a function to get more core memory. If not SEGMENTED,
 *		GETCORE(0) is assumed to return the next available
 *		address. Default is 'sbrk'.
 *	ERRCORE: the error code as returned by GETCORE.
 *		Default is (char *)(-1).
 *	CORESIZE: a desired unit (measured in bytes) to be used
 *		with GETCORE. Default is (1024*ALIGN).
 *
 *	This algorithm is based on a  best fit strategy with lists of
 *	free elts maintained in a self-adjusting binary tree. Each list
 *	contains all elts of the same size. The tree is ordered by size.
 *	For results on self-adjusting trees, see the paper:
 *		Self-Adjusting Binary Trees,
 *		DD Sleator & RE Tarjan, JACM 1985.
 *
 *	The header of a block contains the size of the data part in bytes.
 *	Since the size of a block is 0%4, the low two bits of the header
 *	are free and used as follows:
 *
 *		BIT0:	1 for busy (block is in use), 0 for free.
 *		BIT1:	if the block is busy, this bit is 1 if the
 *			preceding block in contiguous memory is free.
 *			Otherwise, it is always 0.
 */

#include "lint.h"
#include "mallint.h"
#include "mtlib.h"

/*
 * Some abusers of the system (notably java1.2) acquire __malloc_lock
 * in order to prevent threads from holding it while they are being
 * suspended via thr_suspend() or thr_suspend_allmutators().
 * This never worked when alternate malloc() libraries were used
 * because they don't use __malloc_lock for their locking strategy.
 * We leave __malloc_lock as an external variable to satisfy these
 * old programs, but we define a new lock, private to libc, to do the
 * real locking: libc_malloc_lock.  This puts libc's malloc() package
 * on the same footing as all other malloc packages.
 */
mutex_t __malloc_lock = DEFAULTMUTEX;
mutex_t libc_malloc_lock = DEFAULTMUTEX;

static TREE	*Root,		/* root of the free tree */
		*Bottom,	/* the last free chunk in the arena */
		*_morecore(size_t);	/* function to get more core */

static char	*Baddr;		/* current high address of the arena */
static char	*Lfree;		/* last freed block with data intact */

static void	t_delete(TREE *);
static void	t_splay(TREE *);
static void	realfree(void *);
static void	cleanfree(void *);
static void	*_malloc_unlocked(size_t);

#define	FREESIZE (1<<5) /* size for preserving free blocks until next malloc */
#define	FREEMASK FREESIZE-1

static void *flist[FREESIZE];	/* list of blocks to be freed on next malloc */
static int freeidx;		/* index of free blocks in flist % FREESIZE */

/*
 * Interfaces used only by atfork_init() functions.
 */
void
malloc_locks(void)
{
	(void) mutex_lock(&libc_malloc_lock);
}

void
malloc_unlocks(void)
{
	(void) mutex_unlock(&libc_malloc_lock);
}

/*
 *	Allocation of small blocks
 */
static TREE	*List[MINSIZE/WORDSIZE-1]; /* lists of small blocks */

static void *
_smalloc(size_t size)
{
	TREE	*tp;
	size_t	i;

	ASSERT(size % WORDSIZE == 0);
	/* want to return a unique pointer on malloc(0) */
	if (size == 0)
		size = WORDSIZE;

	/* list to use */
	i = size / WORDSIZE - 1;

	if (List[i] == NULL) {
		TREE *np;
		int n;
		/* number of blocks to get at one time */
#define	NPS (WORDSIZE*8)
		ASSERT((size + WORDSIZE) * NPS >= MINSIZE);

		/* get NPS of these block types */
		if ((List[i] = _malloc_unlocked((size + WORDSIZE) * NPS)) == 0)
			return (0);

		/* make them into a link list */
		for (n = 0, np = List[i]; n < NPS; ++n) {
			tp = np;
			SIZE(tp) = size;
			np = NEXT(tp);
			AFTER(tp) = np;
		}
		AFTER(tp) = NULL;
	}

	/* allocate from the head of the queue */
	tp = List[i];
	List[i] = AFTER(tp);
	SETBIT0(SIZE(tp));
	return (DATA(tp));
}

void *
malloc(size_t size)
{
	void *ret;

	if (!primary_link_map) {
		errno = ENOTSUP;
		return (NULL);
	}
	assert_no_libc_locks_held();
	(void) mutex_lock(&libc_malloc_lock);
	ret = _malloc_unlocked(size);
	(void) mutex_unlock(&libc_malloc_lock);
	return (ret);
}

static void *
_malloc_unlocked(size_t size)
{
	size_t	n;
	TREE	*tp, *sp;
	size_t	o_bit1;

	COUNT(nmalloc);
	ASSERT(WORDSIZE == ALIGN);

	/* check for size that could overflow calculations */
	if (size > MAX_MALLOC) {
		errno = ENOMEM;
		return (NULL);
	}

	/* make sure that size is 0 mod ALIGN */
	ROUND(size);

	/* see if the last free block can be used */
	if (Lfree) {
		sp = BLOCK(Lfree);
		n = SIZE(sp);
		CLRBITS01(n);
		if (n == size) {
			/*
			 * exact match, use it as is
			 */
			freeidx = (freeidx + FREESIZE - 1) &
			    FREEMASK; /* 1 back */
			flist[freeidx] = Lfree = NULL;
			return (DATA(sp));
		} else if (size >= MINSIZE && n > size) {
			/*
			 * got a big enough piece
			 */
			freeidx = (freeidx + FREESIZE - 1) &
			    FREEMASK; /* 1 back */
			flist[freeidx] = Lfree = NULL;
			o_bit1 = SIZE(sp) & BIT1;
			SIZE(sp) = n;
			goto leftover;
		}
	}
	o_bit1 = 0;

	/* perform free's of space since last malloc */
	cleanfree(NULL);

	/* small blocks */
	if (size < MINSIZE)
		return (_smalloc(size));

	/* search for an elt of the right size */
	sp = NULL;
	n  = 0;
	if (Root) {
		tp = Root;
		for (;;) {
			/* branch left */
			if (SIZE(tp) >= size) {
				if (n == 0 || n >= SIZE(tp)) {
					sp = tp;
					n = SIZE(tp);
				}
				if (LEFT(tp))
					tp = LEFT(tp);
				else
					break;
			} else { /* branch right */
				if (RIGHT(tp))
					tp = RIGHT(tp);
				else
					break;
			}
		}

		if (sp) {
			t_delete(sp);
		} else if (tp != Root) {
			/* make the searched-to element the root */
			t_splay(tp);
			Root = tp;
		}
	}

	/* if found none fitted in the tree */
	if (!sp) {
		if (Bottom && size <= SIZE(Bottom)) {
			sp = Bottom;
			CLRBITS01(SIZE(sp));
		} else if ((sp = _morecore(size)) == NULL) /* no more memory */
			return (NULL);
	}

	/* tell the forward neighbor that we're busy */
	CLRBIT1(SIZE(NEXT(sp)));

	ASSERT(ISBIT0(SIZE(NEXT(sp))));

leftover:
	/* if the leftover is enough for a new free piece */
	if ((n = (SIZE(sp) - size)) >= MINSIZE + WORDSIZE) {
		n -= WORDSIZE;
		SIZE(sp) = size;
		tp = NEXT(sp);
		SIZE(tp) = n|BIT0;
		realfree(DATA(tp));
	} else if (BOTTOM(sp))
		Bottom = NULL;

	/* return the allocated space */
	SIZE(sp) |= BIT0 | o_bit1;
	return (DATA(sp));
}


/*
 * realloc().
 *
 * If the block size is increasing, we try forward merging first.
 * This is not best-fit but it avoids some data recopying.
 */
void *
realloc(void *old, size_t size)
{
	TREE	*tp, *np;
	size_t	ts;
	char	*new;

	if (!primary_link_map) {
		errno = ENOTSUP;
		return (NULL);
	}

	assert_no_libc_locks_held();
	COUNT(nrealloc);

	/* check for size that could overflow calculations */
	if (size > MAX_MALLOC) {
		errno = ENOMEM;
		return (NULL);
	}

	/* pointer to the block */
	(void) mutex_lock(&libc_malloc_lock);
	if (old == NULL) {
		new = _malloc_unlocked(size);
		(void) mutex_unlock(&libc_malloc_lock);
		return (new);
	}

	/* perform free's of space since last malloc */
	cleanfree(old);

	/* make sure that size is 0 mod ALIGN */
	ROUND(size);

	tp = BLOCK(old);
	ts = SIZE(tp);

	/* if the block was freed, data has been destroyed. */
	if (!ISBIT0(ts)) {
		(void) mutex_unlock(&libc_malloc_lock);
		return (NULL);
	}

	/* nothing to do */
	CLRBITS01(SIZE(tp));
	if (size == SIZE(tp)) {
		SIZE(tp) = ts;
		(void) mutex_unlock(&libc_malloc_lock);
		return (old);
	}

	/* special cases involving small blocks */
	if (size < MINSIZE || SIZE(tp) < MINSIZE) {
		/* free is size is zero */
		if (size == 0) {
			SETOLD01(SIZE(tp), ts);
			_free_unlocked(old);
			(void) mutex_unlock(&libc_malloc_lock);
			return (NULL);
		} else {
			goto call_malloc;
		}
	}

	/* block is increasing in size, try merging the next block */
	if (size > SIZE(tp)) {
		np = NEXT(tp);
		if (!ISBIT0(SIZE(np))) {
			ASSERT(SIZE(np) >= MINSIZE);
			ASSERT(!ISBIT1(SIZE(np)));
			SIZE(tp) += SIZE(np) + WORDSIZE;
			if (np != Bottom)
				t_delete(np);
			else
				Bottom = NULL;
			CLRBIT1(SIZE(NEXT(np)));
		}

#ifndef SEGMENTED
		/* not enough & at TRUE end of memory, try extending core */
		if (size > SIZE(tp) && BOTTOM(tp) && GETCORE(0) == Baddr) {
			Bottom = tp;
			if ((tp = _morecore(size)) == NULL) {
				tp = Bottom;
				Bottom = NULL;
				}
		}
#endif
	}

	/* got enough space to use */
	if (size <= SIZE(tp)) {
		size_t n;

chop_big:
		if ((n = (SIZE(tp) - size)) >= MINSIZE + WORDSIZE) {
			n -= WORDSIZE;
			SIZE(tp) = size;
			np = NEXT(tp);
			SIZE(np) = n|BIT0;
			realfree(DATA(np));
		} else if (BOTTOM(tp))
			Bottom = NULL;

		/* the previous block may be free */
		SETOLD01(SIZE(tp), ts);
		(void) mutex_unlock(&libc_malloc_lock);
		return (old);
	}

	/* call malloc to get a new block */
call_malloc:
	SETOLD01(SIZE(tp), ts);
	if ((new = _malloc_unlocked(size)) != NULL) {
		CLRBITS01(ts);
		if (ts > size)
			ts = size;
		MEMCOPY(new, old, ts);
		_free_unlocked(old);
		(void) mutex_unlock(&libc_malloc_lock);
		return (new);
	}

	/*
	 * Attempt special case recovery allocations since malloc() failed:
	 *
	 * 1. size <= SIZE(tp) < MINSIZE
	 *	Simply return the existing block
	 * 2. SIZE(tp) < size < MINSIZE
	 *	malloc() may have failed to allocate the chunk of
	 *	small blocks. Try asking for MINSIZE bytes.
	 * 3. size < MINSIZE <= SIZE(tp)
	 *	malloc() may have failed as with 2.  Change to
	 *	MINSIZE allocation which is taken from the beginning
	 *	of the current block.
	 * 4. MINSIZE <= SIZE(tp) < size
	 *	If the previous block is free and the combination of
	 *	these two blocks has at least size bytes, then merge
	 *	the two blocks copying the existing contents backwards.
	 */
	CLRBITS01(SIZE(tp));
	if (SIZE(tp) < MINSIZE) {
		if (size < SIZE(tp)) {			/* case 1. */
			SETOLD01(SIZE(tp), ts);
			(void) mutex_unlock(&libc_malloc_lock);
			return (old);
		} else if (size < MINSIZE) {		/* case 2. */
			size = MINSIZE;
			goto call_malloc;
		}
	} else if (size < MINSIZE) {			/* case 3. */
		size = MINSIZE;
		goto chop_big;
	} else if (ISBIT1(ts) &&
	    (SIZE(np = LAST(tp)) + SIZE(tp) + WORDSIZE) >= size) {
		ASSERT(!ISBIT0(SIZE(np)));
		t_delete(np);
		SIZE(np) += SIZE(tp) + WORDSIZE;
		/*
		 * Since the copy may overlap, use memmove() if available.
		 * Otherwise, copy by hand.
		 */
		(void) memmove(DATA(np), old, SIZE(tp));
		old = DATA(np);
		tp = np;
		CLRBIT1(ts);
		goto chop_big;
	}
	SETOLD01(SIZE(tp), ts);
	(void) mutex_unlock(&libc_malloc_lock);
	return (NULL);
}


/*
 * realfree().
 *
 * Coalescing of adjacent free blocks is done first.
 * Then, the new free block is leaf-inserted into the free tree
 * without splaying. This strategy does not guarantee the amortized
 * O(nlogn) behaviour for the insert/delete/find set of operations
 * on the tree. In practice, however, free is much more infrequent
 * than malloc/realloc and the tree searches performed by these
 * functions adequately keep the tree in balance.
 */
static void
realfree(void *old)
{
	TREE	*tp, *sp, *np;
	size_t	ts, size;

	COUNT(nfree);

	/* pointer to the block */
	tp = BLOCK(old);
	ts = SIZE(tp);
	if (!ISBIT0(ts))
		return;
	CLRBITS01(SIZE(tp));

	/* small block, put it in the right linked list */
	if (SIZE(tp) < MINSIZE) {
		ASSERT(SIZE(tp) / WORDSIZE >= 1);
		ts = SIZE(tp) / WORDSIZE - 1;
		AFTER(tp) = List[ts];
		List[ts] = tp;
		return;
	}

	/* see if coalescing with next block is warranted */
	np = NEXT(tp);
	if (!ISBIT0(SIZE(np))) {
		if (np != Bottom)
			t_delete(np);
		SIZE(tp) += SIZE(np) + WORDSIZE;
	}

	/* the same with the preceding block */
	if (ISBIT1(ts)) {
		np = LAST(tp);
		ASSERT(!ISBIT0(SIZE(np)));
		ASSERT(np != Bottom);
		t_delete(np);
		SIZE(np) += SIZE(tp) + WORDSIZE;
		tp = np;
	}

	/* initialize tree info */
	PARENT(tp) = LEFT(tp) = RIGHT(tp) = LINKFOR(tp) = NULL;

	/* the last word of the block contains self's address */
	*(SELFP(tp)) = tp;

	/* set bottom block, or insert in the free tree */
	if (BOTTOM(tp))
		Bottom = tp;
	else {
		/* search for the place to insert */
		if (Root) {
			size = SIZE(tp);
			np = Root;
			for (;;) {
				if (SIZE(np) > size) {
					if (LEFT(np))
						np = LEFT(np);
					else {
						LEFT(np) = tp;
						PARENT(tp) = np;
						break;
					}
				} else if (SIZE(np) < size) {
					if (RIGHT(np))
						np = RIGHT(np);
					else {
						RIGHT(np) = tp;
						PARENT(tp) = np;
						break;
					}
				} else {
					if ((sp = PARENT(np)) != NULL) {
						if (np == LEFT(sp))
							LEFT(sp) = tp;
						else
							RIGHT(sp) = tp;
						PARENT(tp) = sp;
					} else
						Root = tp;

					/* insert to head of list */
					if ((sp = LEFT(np)) != NULL)
						PARENT(sp) = tp;
					LEFT(tp) = sp;

					if ((sp = RIGHT(np)) != NULL)
						PARENT(sp) = tp;
					RIGHT(tp) = sp;

					/* doubly link list */
					LINKFOR(tp) = np;
					LINKBAK(np) = tp;
					SETNOTREE(np);

					break;
				}
			}
		} else
			Root = tp;
	}

	/* tell next block that this one is free */
	SETBIT1(SIZE(NEXT(tp)));

	ASSERT(ISBIT0(SIZE(NEXT(tp))));
}

/*
 * Get more core. Gaps in memory are noted as busy blocks.
 */
static TREE *
_morecore(size_t size)
{
	TREE	*tp;
	ssize_t	n, offset;
	char	*addr;
	ssize_t	nsize;

	/* compute new amount of memory to get */
	tp = Bottom;
	n = (ssize_t)size + 2 * WORDSIZE;
	addr = GETCORE(0);

	if (addr == ERRCORE)
		return (NULL);

	/* need to pad size out so that addr is aligned */
	if ((((uintptr_t)addr) % ALIGN) != 0)
		offset = ALIGN - (uintptr_t)addr % ALIGN;
	else
		offset = 0;

#ifndef SEGMENTED
	/* if not segmented memory, what we need may be smaller */
	if (addr == Baddr) {
		n -= WORDSIZE;
		if (tp != NULL)
			n -= SIZE(tp);
	}
#endif

	/* get a multiple of CORESIZE */
	n = ((n - 1) / CORESIZE + 1) * CORESIZE;
	nsize = n + offset;

	/* check if nsize request could overflow in GETCORE */
	if ((size_t)nsize > MAX_MALLOC - (uintptr_t)addr) {
		errno = ENOMEM;
		return (NULL);
	}

	if ((size_t)nsize <= MAX_GETCORE) {
		if (GETCORE(nsize) == ERRCORE)
			return (NULL);
	} else {
		intptr_t	delta;
		/*
		 * the value required is too big for GETCORE() to deal with
		 * in one go, so use GETCORE() at most 2 times instead.
		 * Argument to GETCORE() must be multiple of ALIGN.
		 * If not, GETCORE(-MAX_GETCORE) will not return brk point
		 * to previous value, but will be ALIGN more.
		 * This would leave a small hole.
		 */
		delta = MAX_GETCORE;
		while (delta > 0) {
			if (GETCORE(delta) == ERRCORE) {
				if (addr != GETCORE(0))
					(void) GETCORE(-MAX_GETCORE);
				return (NULL);
			}
			nsize -= MAX_GETCORE;
			delta = nsize;
		}
	}

	/* contiguous memory */
	if (addr == Baddr) {
		ASSERT(offset == 0);
		if (tp) {
			addr = (char *)tp;
			n += SIZE(tp) + 2 * WORDSIZE;
		} else {
			addr = Baddr - WORDSIZE;
			n += WORDSIZE;
		}
	} else
		addr += offset;

	/* new bottom address */
	Baddr = addr + n;

	/* new bottom block */
	tp = (TREE *)(uintptr_t)addr;
	SIZE(tp) = n - 2 * WORDSIZE;
	ASSERT((SIZE(tp) % ALIGN) == 0);

	/* reserved the last word to head any noncontiguous memory */
	SETBIT0(SIZE(NEXT(tp)));

	/* non-contiguous memory, free old bottom block */
	if (Bottom && Bottom != tp) {
		SETBIT0(SIZE(Bottom));
		realfree(DATA(Bottom));
	}

	return (tp);
}


/*
 * Tree rotation functions (BU: bottom-up, TD: top-down)
 */

#define	LEFT1(x, y)		\
			if ((RIGHT(x) = LEFT(y)) != NULL) PARENT(RIGHT(x)) = x;\
			if ((PARENT(y) = PARENT(x)) != NULL)\
				if (LEFT(PARENT(x)) == x) LEFT(PARENT(y)) = y;\
				else RIGHT(PARENT(y)) = y;\
			LEFT(y) = x; PARENT(x) = y

#define	RIGHT1(x, y)		\
			if ((LEFT(x) = RIGHT(y)) != NULL) PARENT(LEFT(x)) = x;\
			if ((PARENT(y) = PARENT(x)) != NULL)\
				if (LEFT(PARENT(x)) == x) LEFT(PARENT(y)) = y;\
				else RIGHT(PARENT(y)) = y;\
			RIGHT(y) = x; PARENT(x) = y

#define	BULEFT2(x, y, z)	\
			if ((RIGHT(x) = LEFT(y)) != NULL) PARENT(RIGHT(x)) = x;\
			if ((RIGHT(y) = LEFT(z)) != NULL) PARENT(RIGHT(y)) = y;\
			if ((PARENT(z) = PARENT(x)) != NULL)\
				if (LEFT(PARENT(x)) == x) LEFT(PARENT(z)) = z;\
				else RIGHT(PARENT(z)) = z;\
			LEFT(z) = y; PARENT(y) = z; LEFT(y) = x; PARENT(x) = y

#define	BURIGHT2(x, y, z)	\
			if ((LEFT(x) = RIGHT(y)) != NULL) PARENT(LEFT(x)) = x;\
			if ((LEFT(y) = RIGHT(z)) != NULL) PARENT(LEFT(y)) = y;\
			if ((PARENT(z) = PARENT(x)) != NULL)\
				if (LEFT(PARENT(x)) == x) LEFT(PARENT(z)) = z;\
				else RIGHT(PARENT(z)) = z;\
			RIGHT(z) = y; PARENT(y) = z; RIGHT(y) = x; PARENT(x) = y

#define	TDLEFT2(x, y, z)	\
			if ((RIGHT(y) = LEFT(z)) != NULL) PARENT(RIGHT(y)) = y;\
			if ((PARENT(z) = PARENT(x)) != NULL)\
				if (LEFT(PARENT(x)) == x) LEFT(PARENT(z)) = z;\
				else RIGHT(PARENT(z)) = z;\
			PARENT(x) = z; LEFT(z) = x;

#define	TDRIGHT2(x, y, z)	\
			if ((LEFT(y) = RIGHT(z)) != NULL) PARENT(LEFT(y)) = y;\
			if ((PARENT(z) = PARENT(x)) != NULL)\
				if (LEFT(PARENT(x)) == x) LEFT(PARENT(z)) = z;\
				else RIGHT(PARENT(z)) = z;\
			PARENT(x) = z; RIGHT(z) = x;

/*
 * Delete a tree element
 */
static void
t_delete(TREE *op)
{
	TREE	*tp, *sp, *gp;

	/* if this is a non-tree node */
	if (ISNOTREE(op)) {
		tp = LINKBAK(op);
		if ((sp = LINKFOR(op)) != NULL)
			LINKBAK(sp) = tp;
		LINKFOR(tp) = sp;
		return;
	}

	/* make op the root of the tree */
	if (PARENT(op))
		t_splay(op);

	/* if this is the start of a list */
	if ((tp = LINKFOR(op)) != NULL) {
		PARENT(tp) = NULL;
		if ((sp = LEFT(op)) != NULL)
			PARENT(sp) = tp;
		LEFT(tp) = sp;

		if ((sp = RIGHT(op)) != NULL)
			PARENT(sp) = tp;
		RIGHT(tp) = sp;

		Root = tp;
		return;
	}

	/* if op has a non-null left subtree */
	if ((tp = LEFT(op)) != NULL) {
		PARENT(tp) = NULL;

		if (RIGHT(op)) {
			/* make the right-end of the left subtree its root */
			while ((sp = RIGHT(tp)) != NULL) {
				if ((gp = RIGHT(sp)) != NULL) {
					TDLEFT2(tp, sp, gp);
					tp = gp;
				} else {
					LEFT1(tp, sp);
					tp = sp;
				}
			}

			/* hook the right subtree of op to the above elt */
			RIGHT(tp) = RIGHT(op);
			PARENT(RIGHT(tp)) = tp;
		}
	} else if ((tp = RIGHT(op)) != NULL)	/* no left subtree */
		PARENT(tp) = NULL;

	Root = tp;
}

/*
 * Bottom up splaying (simple version).
 * The basic idea is to roughly cut in half the
 * path from Root to tp and make tp the new root.
 */
static void
t_splay(TREE *tp)
{
	TREE	*pp, *gp;

	/* iterate until tp is the root */
	while ((pp = PARENT(tp)) != NULL) {
		/* grandparent of tp */
		gp = PARENT(pp);

		/* x is a left child */
		if (LEFT(pp) == tp) {
			if (gp && LEFT(gp) == pp) {
				BURIGHT2(gp, pp, tp);
			} else {
				RIGHT1(pp, tp);
			}
		} else {
			ASSERT(RIGHT(pp) == tp);
			if (gp && RIGHT(gp) == pp) {
				BULEFT2(gp, pp, tp);
			} else {
				LEFT1(pp, tp);
			}
		}
	}
}


/*
 *	free().
 *	Performs a delayed free of the block pointed to
 *	by old. The pointer to old is saved on a list, flist,
 *	until the next malloc or realloc. At that time, all the
 *	blocks pointed to in flist are actually freed via
 *	realfree(). This allows the contents of free blocks to
 *	remain undisturbed until the next malloc or realloc.
 */
void
free(void *old)
{
	if (!primary_link_map) {
		errno = ENOTSUP;
		return;
	}
	assert_no_libc_locks_held();
	(void) mutex_lock(&libc_malloc_lock);
	_free_unlocked(old);
	(void) mutex_unlock(&libc_malloc_lock);
}


void
_free_unlocked(void *old)
{
	int	i;

	if (old == NULL)
		return;

	/*
	 * Make sure the same data block is not freed twice.
	 * 3 cases are checked.  It returns immediately if either
	 * one of the conditions is true.
	 *	1. Last freed.
	 *	2. Not in use or freed already.
	 *	3. In the free list.
	 */
	if (old == Lfree)
		return;
	if (!ISBIT0(SIZE(BLOCK(old))))
		return;
	for (i = 0; i < freeidx; i++)
		if (old == flist[i])
			return;

	if (flist[freeidx] != NULL)
		realfree(flist[freeidx]);
	flist[freeidx] = Lfree = old;
	freeidx = (freeidx + 1) & FREEMASK; /* one forward */
}

/*
 * cleanfree() frees all the blocks pointed to be flist.
 *
 * realloc() should work if it is called with a pointer
 * to a block that was freed since the last call to malloc() or
 * realloc(). If cleanfree() is called from realloc(), ptr
 * is set to the old block and that block should not be
 * freed since it is actually being reallocated.
 */
static void
cleanfree(void *ptr)
{
	char	**flp;

	flp = (char **)&(flist[freeidx]);
	for (;;) {
		if (flp == (char **)&(flist[0]))
			flp = (char **)&(flist[FREESIZE]);
		if (*--flp == NULL)
			break;
		if (*flp != ptr)
			realfree(*flp);
		*flp = NULL;
	}
	freeidx = 0;
	Lfree = NULL;
}
